﻿using System;
using UnityEngine;
using System.Collections.Generic;
namespace GX
{
    public partial class AssetManagerService : IAssetManager
    {
        public enum AssetAccessMode
        {
            None = 0,
            Resources = 1,
            Stream = 2,
            Cache = 4,
            File = 8,
            Web = 16,
            All = ~(1 << 31),
        }

        public const int PREFERD_FPS = 30;//以FPS为30计
        IAssetManifest _config;
        MonoBehaviour _behaviour;
        readonly AssetDispatcher _assetLoadedDispatcher = new AssetDispatcher();//资源回调
        readonly AssetDispatcher _progressDispatcher = new AssetDispatcher();//进度回调

        Dictionary<string, AssetInfo> _assetInfoMap = new Dictionary<string, AssetInfo>();
        LinkedList<AssetInfo> _waitingList = new LinkedList<AssetInfo>();
        int _numWaiting = 0;
        int _numLoading = 0;

        bool _isUnloadingUnusedAsset = false;
        Dictionary<AssetInfo, int> _toUnloadFrame = new Dictionary<AssetInfo, int>();
        LinkedList<AssetInfo> _toUnloadList = new LinkedList<AssetInfo>();

        int _lastUpdateFrameCount;

        Action<string> _onProgress;
        
        public IAssetManifest assetManifest { get { return _config; } }
        public void Initalize(MonoBehaviour behaviour, IAssetManifest assetManifest)
        {
            _onProgress = OnProgress;
            _behaviour = behaviour;
            _config = assetManifest;
            TickManager.Forever(_config.delayUnloadSeconds * PREFERD_FPS,Update);
        }
        AssetInfo GetAssetInfo(string path)
        {
            string bundleName;
            if (null != _config)
                _config.TryGetAssetBundleName(path, out bundleName);
            else
                bundleName = path;
            AssetInfo info = null;
            _assetInfoMap.TryGetValue(bundleName, out info);
            return info;
        }
        AssetInfo GetOrCreateAssetInfo(string assetPath, bool autoCreate = true)
        {
            string bundleName = null;
            if (null != _config)
                _config.TryGetAssetBundleName(assetPath, out bundleName);
            else
                bundleName = assetPath;

            AssetInfo abInfo = null;
            if (_assetInfoMap.TryGetValue(bundleName, out abInfo) || !autoCreate)
                return abInfo;
            abInfo = new AssetInfo(bundleName, this);
            _assetInfoMap[bundleName] = abInfo;
            var deps = null == _config ? null : _config.GetDirectDependencies(bundleName);
            if (null != deps)
            {
                for (int i = 0; i < deps.Length; ++i)
                {
                    abInfo.AddDependency(GetOrCreateAssetInfo(deps[i]));
                }
            }
            return abInfo;
        }
        public void Retain(string assetPath)
        {
            var abInfo = GetOrCreateAssetInfo(assetPath);
            abInfo.Retain();
        }
        public void Release(string assetPath)
        {
            var abInfo = GetOrCreateAssetInfo(assetPath, false);
            if (null != abInfo)
                abInfo.Release();
        }
        public void Load(string assetPath, AssetDelegate onDone, AssetDelegate onProgress = null)
        {
            var assetInfo = GetOrCreateAssetInfo(assetPath);
            assetInfo.Retain();

            if (assetInfo.isDone)
            {
                if (null != onDone)
                    onDone(assetInfo);
                return;
            }
            _assetLoadedDispatcher.AddListener(assetInfo.path, onDone);
            if (null != onProgress)
                _progressDispatcher.AddListener(assetInfo.path, onProgress);
            LoadInternal(assetInfo);
            TryLoadNext();
        }
        public void LoadBytes(string assetPath, AssetDelegate onDone, AssetDelegate onProgress = null)
        {
            Load(assetPath, onDone, onProgress);
        }
        public void LoadText(string assetPath, AssetDelegate onDone, AssetDelegate onProgress = null)
        {
            Load(assetPath, onDone, onProgress);
        }
        public void Unload(string assetPath, AssetDelegate onDone, AssetDelegate onProgress = null)
        {
            var assetInfo = GetAssetInfo(assetPath);
            if (null != assetInfo)
            {
                assetInfo.Release();
                _assetLoadedDispatcher.RemoveListener(assetInfo.path, onDone);
                if (null != onProgress)
                    _progressDispatcher.RemoveListener(assetInfo.path, onProgress);
            }
        }
        internal void OnRefChange(AssetInfo assetInfo, bool addToUnloadList)
        {
            if (addToUnloadList)
            {
                if (!assetInfo.isDone) return;
                _toUnloadList.AddLast(assetInfo);
                /**1.在回收过程中进来的说明是依赖资源，依赖资源没有引用了直接卸载
                 * 2.否则就是根资源，根资源延迟卸载
                 * */
                if (!_isUnloadingUnusedAsset)
                    _toUnloadFrame[assetInfo] = Time.frameCount + _config.delayUnloadSeconds * AssetManagerService.PREFERD_FPS;
            }
            else
            {
                _toUnloadList.Remove(assetInfo);
                _toUnloadFrame.Remove(assetInfo);
            }
        }
        void LoadInternal(AssetInfo assetInfo)
        {
            var info = assetInfo as AssetInfo;
            if (info.status != AssetStatus.None)
            {
                return;
            }
            _waitingList.AddLast(info);
            info.Wait();
            ++_numWaiting;

            var deps = null == _config ? null : _config.GetDirectDependencies(assetInfo.path);
            if (null != deps)
            {
                for (int i = 0; i < deps.Length; ++i)
                {
                    LoadInternal(GetAssetInfo(deps[i]));
                }
            }
        }
        void TryLoadNext()
        {
            while (_numLoading < assetManifest.numLoadWorks && _numWaiting > 0)
            {
                --_numWaiting;
                ++_numLoading;


                AssetInfo assetInfo = _waitingList.First.Value;
                _waitingList.RemoveFirst();

                assetInfo.BeginLoad(_behaviour, OnLoadDone);
            }
        }
        void OnLoadDone(AssetInfo assetInfo)
        {
            --_numLoading;
            _progressDispatcher.Clear(assetInfo.path);

            if (assetInfo.loadCallCount != 0)//外部主动调过加载才需要回调
            {
                _assetLoadedDispatcher.Invoke(assetInfo, true);
            }
            if (assetInfo.referenceCount == 0)
                OnRefChange(assetInfo, true);
            TryLoadNext();
        }

        public void UnloadUnUsedAssets()
        {
            _isUnloadingUnusedAsset = true;

            var node = _toUnloadList.First;
            bool remove;
            int frameCount;
            bool isDelayUnoad;
            while (null != node)
            {
                var assetInfo = node.Value;
                remove = false;
                if (assetInfo.referenceCount != 0)
                {
                    remove = true;
                }
                isDelayUnoad = _toUnloadFrame.TryGetValue(assetInfo, out frameCount);
                if (!isDelayUnoad || frameCount <= _lastUpdateFrameCount)
                {
                    if (isDelayUnoad)
                        _toUnloadFrame.Remove(assetInfo);

                    remove = true;
                    _assetInfoMap.Remove(assetInfo.path);
                    assetInfo.Unload();
                }
                if (remove)
                {
                    var n = node;
                    node = node.Next;
                    _toUnloadList.Remove(n);
                }
                else
                    node = node.Next;
            }

            _isUnloadingUnusedAsset = true;
        }
        void Update(ITickable ticker)
        {
            _lastUpdateFrameCount = TickManager.frameCount;

            if (null != _progressDispatcher)
                _progressDispatcher.GetKeys(_onProgress);

            UnloadUnUsedAssets();
        }
        void OnProgress(string bundleName)
        {
            var bundleInfo = GetAssetInfo(bundleName);
            if (null != bundleInfo)
                _progressDispatcher.Invoke(bundleInfo, false);
        }
    }
}